查看原文
其他

如何创建自定义Timeline Marker标记

Unity Unity官方平台 2022-05-07

Unity 2019.1开始,Timeline支持标记功能。本文将介绍如何创建自定义Timeline Marker标记。


在《Unity 2019.1新功能:Timeline Signals》中,我们介绍了使用Timeline Signals信号功能来触发事件。在刚开始设计该功能时,为了让该功能正常使用,我们无法使用Timeline的剪辑。


由于信号的一个主要特点是:没有“持续时间”,因此我们给Timeline加入了标记功能。在内部,信号是使用标记来实现的。下面我们介绍如何给Timeline加入自定义标记。


本文相关代码和资源,请访问:

https://github.com/Unity-Technologies/TimelineMarkerCustomization


简单标记

Marker标记是可以向Timeline资源添加的新内容,用于表示一个时间点。如同剪辑分为激活剪辑、音频剪辑、动画剪辑一般,标记也有具体类型。我们可以创建自定义类型的标记,以实现应用于特定工作流程的内容。

 

为了添加新的标记类型,我们只需要创建继承自Marker类的类。

public class SimpleMarker : UnityEngine.Timeline.Marker {}


此自定义标记现在可以添加到Timeline Marker区域的任何轨道。


 

此时,这个简单标记仅仅是可以看到的内容,这意味着该标记在触发时无法运行代码。但这不表示它毫无用处,标记可以用作对齐点或标注。标记也可以在编辑器内和运行时通过Timeline API访问。

 

我们需要把标记和另一个系统结合起来,使它可以执行代码。


Playable Notifications通知系统

Playable API可以在处理PlayableGraph时把通知发送给对象。Playable Notifications通知系统可用于告知目标对象:事件已经发生。


下面我们将构建简单的视图,然后手动发送通知。

 

首先,需要创建一个通知,它是实现INotification接口的类。

public class MyNotification : INotification

{

    public PropertyName id { get; }

}


我们可以使用id属性来识别唯一标识通知。对于本文的示例,我们并不需要该功能,因此将使用默认的实现。

 

然后我们需要一个接收器,它是实现INotificationReceiver接口的类。示例中,接收器会记录接收到通知的时间。

class ReceiverExample : INotificationReceiver

{

   public void OnNotify(Playable origin, INotification notification, object context)

   {

       if (notification != null)

       {

           double time = origin.IsValid() ? origin.GetTime() : 0.0;

           Debug.LogFormat("Received notification of type {0} at time {1}", notification.GetType(), time);

       }

   }

}


在下面的示例中,我们创建了一个新的Playable Graph可运行视图和Playable Output可运行输出。


我们通过使用AddNotificationReceiver方法,把ReceiverExample添加给可运行输出。m_Receiver实例现在可以接收发送到该输出的通知。


现在所有部分都已经为发送通知而准备好,我们可以使用来自可运行输出的PushNotification方法来推送一个新通知。

public class ManualNotification : MonoBehaviour

{

    PlayableGraph m_Graph;

    ReceiverExample m_Receiver;

 

    void Start()

    {

        m_Graph = PlayableGraph.Create("NotificationGraph");

        var output = ScriptPlayableOutput.Create(m_Graph, "NotificationOutput");

 

        //创建和注册接收器

        m_Receiver = new ReceiverExample();

        output.AddNotificationReceiver(m_Receiver);

 

        //在输出推送一个通知

        output.PushNotification(Playable.Null, new MyNotification());

           

        m_Graph.Play();

    }

}


调用PushNotification后不会立即发送通知,它们仅会加入队列排队等候,这表示它们会进行积累,直到视图已经完全处理。


在LateUpdate阶段前,所有队列中的通知都会发送到视图的输出部分。在所有通知都发送后,队列会在新一帧开始前清空。

 

在视图播放的时候,将在m_Receiverinstance实例上调用OnNotify方法,并将通知作为参数发送。在运行模式中转换时,以下信息会出现在控制台:


Received notification of type MyNotification at time 0

 

现在接收器正确接收了通知,我们要如何控制通知发送的时间呢?我们需要更多帮助来实现该效果。


TimeNotificationBehaviour

我们已经知道如何通过可运行视图发送通知,现在我们调度通知,使它在选择的时间内发送。

 

我们可以使用内置类TimeNotificationBehaviour,该类是标准的PlayableBehaviour,所以它可以添加到任意视图,只要使用一些逻辑就可以在准确时间发送通知。

 

让我们调整之前的示例。

public class ScheduledNotification : MonoBehaviour

{

   PlayableGraph m_Graph;

   ReceiverExample m_Receiver;

 

   void Start()

   {

       m_Graph = PlayableGraph.Create("NotificationGraph");

       var output = ScriptPlayableOutput.Create(m_Graph, "NotificationOutput");

     

       //创建和注册接收器

       m_Receiver = new ReceiverExample();

       output.AddNotificationReceiver(m_Receiver);

 

       //创建TimeNotificationBehaviour

       var timeNotificationPlayable = ScriptPlayable<TimeNotificationBehaviour>.Create(m_Graph);

       output.SetSourcePlayable(timeNotificationPlayable);

     

       /在时间通知行为添加通知

       var notificationBehaviour = timeNotificationPlayable.GetBehaviour();

       notificationBehaviour.AddNotification(2.0, new MyNotification());

     

       m_Graph.Play();

   }

}

 

下面是生成的可运行视图。



我们没有在可运行输出上直接调用PushNotification,而是给输出附加TimeNotificationBehaviour,然后给它添加一个通知。该行为会在正确时间自动把通知推送给输出,控制台会显示以下信息:


Received notification of type MyNotification at time 2.00363647006452

 

现在我们可以控制通知的发送时间了。

 

为什么通知不是在准确的第二秒发送呢?在我们给TimeNotificationBehaviour添加通知时,难道没有指定为正好在二秒吗?

notificationBehaviour.AddNotification(2.0, new MyNotification());

 

这是因为AddNotification方法不会确保准确的时间。在Unity开始渲染新一帧时,Playable Graph可运行视图会进行更新。根据游戏的帧率,在通知添加到TimeNotificationBehaviour时,PlayableGraph的估算时间可能不会正好符合指定的时间。

 

AddNotification方法会确保的是:通知将会在PlayableGraph的时间比通知触发时间大的时候发送。


MarkerNotification

如果我们想在可运行视图中手动发送通知的话,一些新API会很实用,但可能要进行很多处理。幸运的是,Timeline可以自动生成合适的PlayableGraph可运行视图来处理通知。

 

现在我们要创建一个新的标记,用来实现INotification接口。

public class NotificationMarker : Marker, INotification

{

   public PropertyName id { get; }

}


继承自Marker并实现INotification的类会告诉Timeline,它需要生成一个可运行视图来支持该通知。

 

如果我们添加该标记给空白的Timeline,将创建出以下可运行视图。

 


这和上文的可运行视图几乎一样,只不过Timeline会添加自己的PlayableBehaviour,这比创建自定义Playable Graph简单多了。

 

剩下的一件事是弄清楚接收通知的对象,相应规则和信号功能相同:

 

  • 如果标记在Timeline的Header区域:拥有PlayableDirector的对象会接收通知,PlayableDirector用于播放当前的Timeline。

 

  • 如果标记在轨道上:关联到轨道的对象会接收通知。

 

实现INotificationReceiver接口并位于目标对象的任意组件会接收通知。

 

在示例中,我们添加了二个NotificationMarker给Timeline Header区域,还给运行Timeline的对象添加了NotificationReceiver。

 

下面是控制台的输出内容:

 

Received notification of type NotificationMarker at time 1.00330553948879

Received notification of type NotificationMarker at time 2.016666666666

 

只有实现INotification接口的标记才会生成合适的Playable Graph可运行视图来支持通知。

 

下面表格展示了创建自定义标记的不同方法的特点。


继承自…

是否可以在编辑器内和运行时通过API(GetMarkers())访问

是否影响Timeline持续时间

是否通过Playable Graph发送

Marker

Marker + INotification


自定义样式

我们的自定义标记会以常见的“图钉”表示,也可以把该图标改为自己选择的图像。下面演示这个过程,创建一个Annotation标记。

 

第一步是创建样式表。样式表可以用来扩展编辑器的可视化外观,我们可以在编辑器文件夹的StyleSheets/Extensions目录下添加common.uss文件。

 

在示例中,我们添加了一个新文件5-Annotation/Editor/Stylesheets/Extensions/common.uss

 

USS即Unity样式表,使用类似CSS的语法来描述新样式,下面是样式的示例。

Annotation

{

   width:18px;

   height:18px;

   background-image: resource("Assets/5-Annotation/Editor/pencil.png");

}


在该样式中,我们指定了使用铅笔图标和大小属性。然后告诉Timeline, 在屏幕上绘制标记时应该使用该样式。

[CustomStyle("Annotation")]

public class Annotation : Marker

{

   [TextArea] public string annotation;

}


CustomStyle属性可用于指定使用什么样式。在示例中,我们希望使用在common.uss文件中添加的Annotation样式。


在给Timeline添加Annotation标记时,它将使用我们创建的自定义样式。


组合效果

为了展示使用标记和通知可以实现的效果,我们给Github代码库添加了Jump标记示例。该标记和JumpReceiver结合起来,它会从Timeline上的一个点“跳到”另一个点。目标点使用Destination标记来指定。


该示例结合了本文中介绍的所有内容,包括自定义样式。图中橙色箭头是跳跃点,而紫色箭头是目标点。


 

通知和标记是给Playable Graph可运行视图和Timeline API添加的强大功能,本文示例可以帮助开发者学习如何创建出有趣的内容。


来自剪辑的通知

PlayableGraph可以发送通知,Timeline可以使用通知来强化标记的功能。但是剪辑是否可以发送通知?

 

答案是肯定的,通知可以从Playable Behaviour发送。 

public class ClipNotificationBehaviour : PlayableBehaviour

{

   double m_PreviousTime;

 

   public override void OnGraphStart(Playable playable)

   {

       m_PreviousTime = 0;

   }

 

   public override void ProcessFrame(Playable playable, FrameData info, object playerData)

   {

       if ((int)m_PreviousTime < (int)playable.GetTime())

       {

           info.output.PushNotification(playable, new MyNotification());

       }

 

       m_PreviousTime = playable.GetTime();

   }

}


在该示例中,我们在剪辑处理期间的每一秒推送了一个通知。如果我们把该剪辑添加给Timeline,把NotificationReceiver添加给驱动Timeline的对象,下面是生成的输出内容:


Received notification of type MyNotification at time 1.01593019999564

Received notification of type MyNotification at time 2.00227000191808

Received notification of type MyNotification at time 3.01137680560353

 

如果你已经有自己的Playable Behaviour类,并且希望发送通知,不一定需要标记,剪辑已经支持大部分功能。


小结

创建自定义Timeline Marker标记为大家介绍到这里,更多Unity 2019最新信息,请关注Unity Connect平台(Connect.unity.com)。


下载Unity Connect APP,请点击此处。 观看部分Unity官方视频,请关注B站帐户:Unity官方


推荐阅读

使用Unity 2019.1中的Timeline Signals

为Timeline创造性地设计脚本

Timeline功能亮点Playable API

在原生iOS或Android应用中将Unity作为库使用

Unity性能优化的最佳实践

使用Unity制作《龙珠》龟派气功冲击波


Unity官方教师培训火热报名中

7月29日-8月2日将举办Unity官方教师培训课程,现诚邀广大教师一同学习分享Unity最新技术,探讨Unity在教育教学中的创新应用。[了解详情......


培训时间:7月29日-8月2日,共5天

报名地址:

https://www.bagevent.com/event/5329696



点个“在看”,表达你的态度

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存